package org.scaffold.velocity;

import de.tototec.cmdoption.CmdCommand;
import de.tototec.cmdoption.CmdOption;
import de.tototec.cmdoption.CmdlineParser;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.util.ClassUtils;
import org.scaffold.config.*;
import org.scaffold.jdbc.DbHandle;
import org.scaffold.jdbc.JdbcTypeNotFoundException;

import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.*;

/**
 * CmdlineParser工厂类
 */
public class CmdlineParserFactory {
    private static final CmdlineParser cmdlineParser = new CmdlineParser();
    private static final Map<String, CmdlineOptionConfig> GENERATOR_MAP = new HashMap<>();


    private static final Log log = LogFactory.getLog(CmdlineParserFactory.class);
    /**
     * 默认的CONFIG_NAME
     */
    private static String DEFAULT_CONFIG_NAME = "mybatis";

    public static CmdlineParser build() throws IllegalAccessException, InstantiationException, IOException {
        initConfig(cmdlineParser);
        return cmdlineParser;
    }

    /**
     * 适配命令
     *
     * @param cmdline 命令
     */
    public static void parse(String cmdline) throws SQLException, IOException, ClassNotFoundException, JdbcTypeNotFoundException {
        final String CMDLINE_SPLIT = " ";
        String genName = DEFAULT_CONFIG_NAME + ".";

        //默认config
        try {
            if (cmdline.startsWith("-")) {
                genName += cmdline.split(CMDLINE_SPLIT)[0];
                cmdline = DEFAULT_CONFIG_NAME + CMDLINE_SPLIT + cmdline;
            } else {
                genName = cmdline.split(CMDLINE_SPLIT)[0] + "." + cmdline.split(CMDLINE_SPLIT)[1];
            }
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
        cmdlineParser.parse(cmdline.split(CMDLINE_SPLIT));
        MybatisGenerateConfig generateConfig = (MybatisGenerateConfig) cmdlineParser.getParsedCommandObject();
        if (GenerateConfig.help) {
            usage();
            GenerateConfig.help = false;
            return;
        }
        try {
            if (GenerateConfig.jdbcType.size() > 0) {
                JdbcTypeOptionConfigHandle jdbcTypeOptionHandle = (JdbcTypeOptionConfigHandle) GENERATOR_MAP.get(genName);
                jdbcTypeOptionHandle.config(generateConfig);
                return;
            }
            if (GENERATOR_MAP.get(genName) instanceof GeneratorOptionConfigHandle) {
                GeneratorOptionConfigHandle generator = (GeneratorOptionConfigHandle) GENERATOR_MAP.get(genName);
                if (generator != null) {
                    generator.generate(generateConfig);
                }
            }
        } catch (Exception e) {
            throw e;
        } finally {
            generateConfig.clearOption();
        }

    }

    public static void usage() {
        cmdlineParser.usage();
    }

    private static final String DEFAULT_GENERATE_PATH_NAME = new File("/scaffold").getPath();

    private static String getDefaultGeneratePath() {
        String homeDirectory = TemplateUtils.getHomeDirectory().getPath();
        return String.format("%s%s", homeDirectory, DEFAULT_GENERATE_PATH_NAME);
    }

    /**
     * 初始化配置
     */
    private static void initConfig(CmdlineParser parser) throws InstantiationException, IllegalAccessException, IOException {
        initConfigFiles();
        MybatisGenerateConfig mybatisGenerateConfig = new MybatisGenerateConfig();
        initGenerator(mybatisGenerateConfig);
        debug("init config for %s", MybatisGenerateConfig.class.toString());
        parser.addObject(mybatisGenerateConfig);
    }

    private static final String CONFIG_PREFIX = "config/";
    private static final String CONFIG_FILE_NAME = "config.properties";
    private static final String JDBC_TYPE_FILE_NAME = "jdbcTypes.properties";
    private static final String HELP_FILE_NAME = "help.txt";
    private static final String ENCODE = "utf-8";

    private static void initConfigFiles() throws IOException {
        File file = new File(CONFIG_PREFIX);
        if (!file.exists() && file.mkdir()) {
            new File(file, CONFIG_FILE_NAME).createNewFile();

            InputStream helpInputStream = ClassUtils.getResourceAsStream(CmdlineParserFactory.class, CONFIG_PREFIX + HELP_FILE_NAME);
            OutputStream helpOutputStream = new FileOutputStream(new File(file, HELP_FILE_NAME));

            InputStream jdbcTypesInputStream = ClassUtils.getResourceAsStream(CmdlineParserFactory.class, CONFIG_PREFIX + JDBC_TYPE_FILE_NAME);
            OutputStream jdbcTypesOutputStream = new FileOutputStream(new File(file, JDBC_TYPE_FILE_NAME));

            InputStream configInputStream = ClassUtils.getResourceAsStream(CmdlineParserFactory.class, CONFIG_PREFIX + CONFIG_FILE_NAME);
            OutputStream configOutputStream = new FileOutputStream(new File(file, CONFIG_FILE_NAME));

            try {
                IOUtils.write(IOUtils.toString(helpInputStream, ENCODE), helpOutputStream, ENCODE);

                IOUtils.write(IOUtils.toString(jdbcTypesInputStream, ENCODE), jdbcTypesOutputStream, ENCODE);

                IOUtils.write(IOUtils.toString(configInputStream, ENCODE), configOutputStream, ENCODE);
            } finally {
                IOUtils.closeQuietly(helpOutputStream);
                IOUtils.closeQuietly(helpInputStream);
                IOUtils.closeQuietly(configOutputStream);
                IOUtils.closeQuietly(configInputStream);
                IOUtils.closeQuietly(jdbcTypesInputStream);
                IOUtils.closeQuietly(jdbcTypesOutputStream);
            }
        }
        InputStream configInputStream = null;
        InputStream jdbcTypesInputStream = null;
        try {
            File configFileRead = new File(file, CONFIG_FILE_NAME);
            if (configFileRead.canRead()) {
                configInputStream = new FileInputStream(configFileRead);
                Properties config = new Properties();
                config.load(configInputStream);
                String generatePath = StringUtils.trimToNull(config.getProperty("generatePath"));
                //默认初始化本地
                GenerateConfig.generatePath = generatePath == null ? getDefaultGeneratePath() : generatePath;
                GenerateConfig.databaseUrl = StringUtils.trimToNull(config.getProperty("databaseUrl"));
            }
            File jdbcTypesFileRead = new File(file, JDBC_TYPE_FILE_NAME);
            if (jdbcTypesFileRead.canRead()) {
                jdbcTypesInputStream = new FileInputStream(jdbcTypesFileRead);
                Properties jdbcTypes = new Properties();
                jdbcTypes.load(jdbcTypesInputStream);
                Set<String> names = jdbcTypes.stringPropertyNames();
                Iterator<String> stringIterator = names.iterator();
                while (stringIterator.hasNext()) {
                    String name = stringIterator.next();
                    DbHandle.setJdbcTypeConfig(name, jdbcTypes.getProperty(name));
                }
            }

        } finally {
            IOUtils.closeQuietly(configInputStream);
            IOUtils.closeQuietly(jdbcTypesInputStream);
        }
    }

    private static void initGenerator(Object o) throws IllegalAccessException, InstantiationException {
        CmdCommand command = o.getClass().getAnnotation(CmdCommand.class);
        if (command != null) {
            String configName = command.names()[0];
            Field[] fields = o.getClass().getFields();
            for (Field field : fields) {
                CmdOption cmdOption = field.getAnnotation(CmdOption.class);
                if (cmdOption != null && CmdlineOptionConfig.class.isAssignableFrom(cmdOption.handler())) {
                    String genKey = configName + ".";
                    for (String opName : cmdOption.names()) {
                        GENERATOR_MAP.put(genKey + opName, (CmdlineOptionConfig) cmdOption.handler().newInstance());
                    }
                }
            }
        }
    }

    public static void debug(String msg, Object... args) {
        if (log.isDebugEnabled()) {
            log.debug(String.format(msg, args));
        }
    }
}
